/* eslint-disable no-use-before-define */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable @typescript-eslint/no-explicit-any */
// import { performance } from 'perf_hooks';
// import { logException, getLogger } from './log';
// import type { EventCallbackFunc } from './CallObject';
// import { isEnvProfile } from './env';
// import type { msgData } from './Utils';

// const NS_PER_SEC = 1e9;
// const NS_PER_MS = 1e6;

let profileGlobal = isEnvProfile();
function setProfile(p: boolean): void {
	console.log('set_profile', p);
	profileGlobal = p;
}
function isProfile(): boolean {
	return profileGlobal;
}

// function webTime(time?: [number, number]): [number, number] {
// 	const tm = new Date().getTime();
// 	const now: [number, number] = [Math.floor(tm / 1000), (tm % 1000) * 1000];
// 	if (!time) {
// 		return now;
// 	}
// 	return [now[0] - time[0], now[1] - time[1]];
// }

// const hrtime = (process && process.hrtime) || webTime;

function hrtime(time?: number) {
	if (!time) {
		return performance.now();
	}
	return performance.now() - time;
}

// 返回ms
function profileCallCost(func: EventCallbackFunc, self: any, args: msgData): [number, number, any] {
	// gen let process = require("process")
	const time = hrtime();
	const rt = func.apply(self, args);
	// const diff = hrtime(time);
	// const cost = diff[0] * NS_PER_SEC + diff[1];
	const cost = hrtime(time);
	return [time, cost, rt];
}

class CostGuard {
	guards: CostGuards;

	constructor(name: string, guards: CostGuards) {
		this.name = name;
		this.guards = guards;
	}

	name: string;

	called = 0; // call how many

	mscost = 0; // 单位毫秒！

	error = 0;

	// return ms
	percost(): number {
		if (this.called === 0) return 0;
		return this.mscost / this.called;
	}

	// virtual...
	// func:Function;

	profileCall(func: EventCallbackFunc, self: unknown, args: msgData): any {
		const [_start, cost, rt] = profileCallCost(func, self, args);
		this.mscost += cost;
		if (cost > this.guards.printns) {
			getLogger('timer').warn('profile cost ', this.name, `${cost.toFixed(2)}ms`);
		}
		if (cost > 1000) {
			getLogger('timer').error('profile cost ', this.name, `${(cost / 1000).toFixed(2)}s`);
		}
		return rt;
	}

	tryCall(func: EventCallbackFunc, self: unknown, args: msgData): any {
		this.called++;
		try {
			if (!this.guards.profile) {
				return func.apply(self, args);
			}
			return this.profileCall(func, self, args);
		} catch (err) {
			this.error++;
			logException(`try_call_data ${this.name}`, err);
			return null;
		}
	}

	running = 0; // counter for shared use!

	trace(logfunc = console.warn): void {
		const cost = (this.mscost / 1000).toFixed(2);
		const per = this.percost().toFixed(2);
		logfunc(
			`name ${this.name} run ${this.running} called ${this.called} cost(s):${cost} percost(ms):${per} error:${this.error}`,
		);
	}
}

class CostGuards {
	// guards  = new Map<string,costGuard>()
	guards: { [key: string]: CostGuard } = {};

	name: string;

	profile = false;

	printns = 10; // 0.01 * NS_PER_SEC;

	constructor(name: string) {
		this.name = name;
	}

	// if not find, auto new!
	findGuard(name: string): CostGuard {
		let guard = this.guards[name];
		if (!guard) {
			guard = new CostGuard(name, this);
			this.guards[name] = guard;
		}
		return guard;
	}

	getGuard(name: string): CostGuard {
		return this.guards[name];
	}

	setProfile(profile: boolean, printns = 10): void {
		this.profile = profile;
		this.printns = printns;
	}

	trace(top = 10, s = 0, per = false, logfunc = console.warn): void {
		costTrace(this.name, this.guards, top, s, per, logfunc);
	}
}

function costTrace(
	name: string,
	handles: { [key: string]: CostGuard },
	top = 10,
	s = 0,
	sortper = false,
	logfunc = console.warn,
): void {
	logfunc('\n');
	const aa: CostGuard[] = Object.values(handles);
	logfunc('cost_trace', aa.length, name, top, s, sortper);

	if (sortper) {
		aa.sort((a: CostGuard, b: CostGuard) => b.percost() - a.percost());
	} else {
		aa.sort((a: CostGuard, b: CostGuard) => b.mscost - a.mscost);
	}

	let index = 0;
	for (const msg of aa) {
		if (msg.mscost >= s * 1000) {
			msg.trace(logfunc);
		}
		index++;
		if (index >= top) break;
	}
	logfunc('\n');
}

let funcLevel = 0;
let funcData: any[] = [];

function mulStr(s: string, mul: number): string {
	let rt = '';
	for (let i = 0; i < mul; i++) rt += s;
	return rt;
}

function traceFuncData(funcname: string): void {
	const start0 = funcData[funcData.length - 1][2];
	for (const data of funcData) {
		const [level, name, start, cost] = data;
		const s = `${mulStr('    ', level) + name} :: ${(start - start0).toFixed(2)}+${cost.toFixed(
			2,
		)}ms`;
		console.warn(s);
		if (cost > 1000) console.error(s);
	}
	funcData = [];
	console.warn('guardFunc end', funcname, '\n');
}

function guardFunc(name: string, f: EventCallbackFunc, printns = 10): any {
	if (!profileGlobal) return f();
	funcLevel += 1;
	const [start, cost, rt] = profileCallCost(f, null, []);
	funcLevel -= 1;
	// console.log(name,cost,printns)
	if (cost >= printns) {
		funcData.push([funcLevel, name, start, cost]);
	}
	if (funcLevel === 0) {
		if (funcData.length > 0) {
			traceFuncData(name);
		}
	}
	return rt;
}

function guardFunc2(name: string, f: EventCallbackFunc, printns = 1): any {
	if (!profileGlobal) return f();
	return guardFunc(name, f, printns);
}
